package org.example.redisson;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;

import java.util.concurrent.TimeUnit;

/**
 * @Classname RedisLockTest
 * @Description redis分布锁
 * @Date 2021/3/24 3:09 下午
 * @Created by xiangjd
 */
@Slf4j
public class RedisLockTest {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    public static final String PRODUCT_ID = "100001";


    /**
     * jvm层面锁
     *
     * @return
     * @throws OrderExpection
     */
    @Test
    public String test1() throws OrderExpection {
        //查看库存
        int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
        //判断大小
        if (stock > 0) {
            //减库存
            stock -= 1;
            stringRedisTemplate.opsForValue().set("stock", String.valueOf(stock));
            log.debug("库存扣减成功,当前库存为:{}", stock);

        } else {
            log.debug("库存扣减失败,没有库存了!");
            throw new OrderExpection("库存扣减失败,没有库存了!");
        }
        return "success";
    }

    /**
     * 引入redis分布式锁 setnx
     *
     * @return
     * @throws OrderExpection
     */
    @Test
    public String test2() throws OrderExpection {
        Boolean ifAbsent = stringRedisTemplate.opsForValue().setIfAbsent(PRODUCT_ID, "shangping");
        if (!ifAbsent) {
            return "failure";
        }
        int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
        //判断大小
        if (stock > 0) {
            //减库存
            stock -= 1;
            stringRedisTemplate.opsForValue().set("stock", String.valueOf(stock));
            log.debug("库存扣减成功,当前库存为:{}", stock);

        } else {
            log.debug("库存扣减失败,没有库存了!");
            throw new OrderExpection("库存扣减失败,没有库存了!");
        }
        stringRedisTemplate.delete(PRODUCT_ID);
        return "success";
    }

    /**
     * 假设当线程A首先执行stringRedisTemplate.opsForValue()的setIfAbsent()方法返回true，继续向下执行，
     * 正在执行业务代码时，抛出了异常，线程A直接退出了JVM。此时，stringRedisTemplate.delete(PRODUCT_ID);
     * 代码还没来得及执行，之后所有的线程进入提交订单的方法时，调用stringRedisTemplate.opsForValue()的setIfAbsent()方法都会返回false。
     * 导致后续的所有下单操作都会失败。这就是分布式场景下的死锁问题。
     * <p>
     * 实际生产环境很复杂,虽然使用finally,但是突然宕机,导致不能优雅的退出jvm,所以不能避免
     *
     * @return
     * @throws OrderExpection
     */
    @Test
    public String test3() throws OrderExpection {
        Boolean ifAbsent = stringRedisTemplate.opsForValue().setIfAbsent(PRODUCT_ID, "shangping");
        if (!ifAbsent) {
            return "failure";
        }
        try {
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            //判断大小
            if (stock > 0) {
                //减库存
                stock -= 1;
                stringRedisTemplate.opsForValue().set("stock", String.valueOf(stock));
                log.debug("库存扣减成功,当前库存为:{}", stock);

            } else {
                log.debug("库存扣减失败,没有库存了!");
                throw new OrderExpection("库存扣减失败,没有库存了!");
            }
        } finally {
            stringRedisTemplate.delete(PRODUCT_ID);
        }
        return "success";
    }


    /**
     * 引入redis超时机制解决宕机造成的死锁问题
     *
     * @return
     * @throws OrderExpection
     */
    @Test
    public String test4() throws OrderExpection {
        Boolean ifAbsent = stringRedisTemplate.opsForValue().setIfAbsent(PRODUCT_ID, "shangping");
        if (!ifAbsent) {
            return "failure";
        }
        try {
            //设计30秒超时时间
            stringRedisTemplate.expire(PRODUCT_ID, 30, TimeUnit.SECONDS);
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            //判断大小
            if (stock > 0) {
                //减库存
                stock -= 1;
                stringRedisTemplate.opsForValue().set("stock", String.valueOf(stock));
                log.debug("库存扣减成功,当前库存为:{}", stock);

            } else {
                log.debug("库存扣减失败,没有库存了!");
                throw new OrderExpection("库存扣减失败,没有库存了!");
            }
        } finally {
            stringRedisTemplate.delete(PRODUCT_ID);
        }
        return "success";
    }

    /**
     * 我们在向Redis中设置锁标志位的时候就设置了超时时间
     * @return
     * @throws OrderExpection
     */
    @Test
    public String test5() throws OrderExpection {
        Boolean ifAbsent = stringRedisTemplate.opsForValue().setIfAbsent(PRODUCT_ID, "shangping",30,TimeUnit.SECONDS);
        if (!ifAbsent) {
            return "failure";
        }
        try {
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            //判断大小
            if (stock > 0) {
                //减库存
                stock -= 1;
                stringRedisTemplate.opsForValue().set("stock", String.valueOf(stock));
                log.debug("库存扣减成功,当前库存为:{}", stock);

            } else {
                log.debug("库存扣减失败,没有库存了!");
                throw new OrderExpection("库存扣减失败,没有库存了!");
            }
        } finally {
            stringRedisTemplate.delete(PRODUCT_ID);
        }
        return "success";
    }
}
